﻿*      /// <summary>
*      /// Represents a snippet of code - typically the text within the text box
*      /// of the UI
*      /// </summary>

*      The "final" or "is final" clause is equivalent to C#'s "sealed" modifier.
 
       class-id. Snippet as "Barbarian.Snippet" final.

       environment division.
       configuration section.
       repository.
           class SnippetOptions     as "Barbarian.SnippetOptions"
* TODO: Find out what DLL holds the COBOLCodeProvider. Decide whether to use it.
*       If we do use it, we might need to branch the code. Many people will
*       not have this DLL, and may not want to install it.                         
           class COBOLCodeProvider  as "MicroFocus.COBOL.CodeDom.COBOLCodeProvider"
           class CSharpCodeProvider as "Microsoft.CSharp.CSharpCodeProvider"
           class VBCodeProvider     as "Microsoft.VisualBasic.VBCodeProvider"
           class CppCodeProvider    as "Microsoft.VisualC.CppCodeProvider"
           class CodeDomProvider    as "System.CodeDom.Compiler.CodeDomProvider"
           class CompilerParameters as "System.CodeDom.Compiler.CompilerParameters"
           class CompilerResults    as "System.CodeDom.Compiler.CompilerResults"
           class Path               as "System.IO.Path"
           class StringReader       as "System.IO.StringReader"
           class Regex              as "System.Text.RegularExpressions.Regex"
           class StringBuilder      as "System.Text.StringBuilder"
           class Application        as "System.Windows.Forms.Application"
           class MessageBox         as "System.Windows.MessageBox"
           .
           
       factory.
       data division.
       working-storage section.

*      Constants in Micro Focus COBOL are 78-level items. However, only literals
*      can be used to initialize constants, and we need to concatenate literals
*      with System.Environment::Newline. We'll use 01-level items.

       01  _annotationExpression  Regex  value new Regex(" *\|?#[A-Z0-9]\s*$").
       01  _nl                    string value type "System.Environment"::"NewLine".

*      Note how we use _nl to initialize other working storage variables :       

       01  _LineFormat          string value string::"Concat"("        {0}", _nl).

       01  _cppUsingFormat      string value string::"Concat"("using namespace {0};", _nl).
       01  _cppLinePragmaFormat string value string::"Concat"("#line {0}", _nl).
       01  _cppClassProlog      string value string::"Concat"(_nl, "ref class Snippet", _nl, "{", _nl).
       01  _cppMainProlog       string value string::"Concat"("public:", _nl,
                                                               "    [STAThread()]", _nl,
                                                               "    void Main(void)", _nl,       
                                                               "    {", _nl).
       01  _cppMainEpilog       string value string::"Concat"("    }", _nl).
       01  _cppClassEpilog      string value string::"Concat"("};", _nl).

       01  _csUsingFormat       string value string::"Concat"("using {0};", _nl).
       01  _csLinePragmaFormat  string value string::"Concat"("#line {0}", _nl).
       01  _csClassProlog       string value string::"Concat"(_nl, "public class Snippet", _nl, "{", _nl).
       01  _csMainProlog        string value string::"Concat"("    [STAThread()]", _nl,
                                                              "    public static void Main()", _nl,       
                                                              "    {", _nl).
       01  _csMainEpilog        string value string::"Concat"("    }", _nl).
       01  _csClassEpilog       string value string::"Concat"("}", _nl).

       01  _vbImportsFormat     string value string::"Concat"("Imports {0}", _nl).
* TODO: Find out if VB supports line pragma. It's yielding errors.       
*      01  _vbLinePragmaFormat  string value string::"Concat"("#line {0}", _nl).
       01  _vbLinePragmaFormat  string value string::"Empty".
       01  _vbModuleProlog      string value string::"Concat"(_nl, "Module Snippet", _nl, _nl).
       01  _vbMainProlog        string value string::"Concat"("    <STAThread()> _", _nl,
                                                              "    Sub Main()", _nl).
       01  _vbMainEpilog        string value string::"Concat"("    End Sub", _nl, _nl).
       01  _vbModuleEpilog      string value string::"Concat"("End Module", _nl).

       procedure division.
       
         method-id. "CreateNew" public.
         linkage section.
         01  language     string.
         01  snippetText  string.
         01  options      SnippetOptions.
         01  snippet      object reference active-class.
         procedure division using language, snippetText, options returning snippet.

             invoke self::"New"() returning snippet.
             invoke snippet::"Initialize"(language, snippetText, options)

         end method "CreateNew".
         
*        /// <summary>
*        /// Removes the annotations from each line of code. These are of the form:
*        /// (any number of spaces)(optional |)#(digit)(end of line)
*        /// </summary>

*      The "internal" or "is internal" clause allows access from within the
*      same compilation unit, i.e., from within the same assembly.       
        
       method-id. "RemoveAnnotations" internal.
       working-storage section.
       01  builder      StringBuilder  value new StringBuilder.
       01  reader       StringReader.
       01  currentLine  string.
       01  ex           type "System.Exception".
       procedure division using s as string returning result as string.

           try
               set reader to new StringReader(s)
               set currentLine to reader::"ReadLine"()

               perform with test before until currentLine = null
                   set currentLine to _annotationExpression::"Replace"(currentLine, string::"Empty")
                   invoke builder::"AppendLine"(currentLine)
                   set currentLine to reader::"ReadLine"()
               end-perform

           catch ex
               invoke type "System.Console"::"WriteLine"(string::"Concat"("Exception: ", ex::"Message"))
           finally
               if not reader = null then invoke reader::"Dispose"()
           end-try
           
           set result to builder::"ToString"()
           
       end method "RemoveAnnotations".
       
       end factory.

*-------------------------------------------------------------------------------
             
       object.

       data division.
       working-storage section.
       01  _language     string.
       01  _snippetText  string.
       01  _options      SnippetOptions.

       procedure division.
       
           method-id. get property "Compiler" is private.
           procedure division returning result as CodeDomProvider.
               if _language = "C#"
                   set result to new CSharpCodeProvider(_options::"CompilerOptions")
                   goback
               end-if
               if _language = "VB"
                    set result to new VBCodeProvider(_options::"CompilerOptions")
                    goback
               end-if
           end method.

           method-id. "Initialize".
           linkage section.
           01  language     string.
           01  snippetText  string.
           01  options      SnippetOptions.
           procedure division using language, snippetText, options.
           
               set _language to language
               set _snippetText to snippetText
               set _options to options
               
           end method "Initialize".

           method-id. "GenerateCode" public.
           working-storage section.
           01  builder     StringBuilder  value new StringBuilder.
           01  reader      StringReader   value new StringReader(Snippet::"RemoveAnnotations"(_snippetText)).
           01  nsList      type "System.Collections.Generic.List"[string].
           01  nonMain     string         value string::"Empty".
           01  namespaces  string.
           01  currentLine string.
           01  lineNumber  binary-long    value 1.
           01  i           binary-long    value 0.
           linkage section.
           01  includeSnippetLineNumbers  condition-value.
           01  result                     string.
           procedure division using includeSnippetLineNumbers returning result.

               set nsList to _options::"Namespaces" as type "System.Collections.Generic.List"[string]

               perform with test before until i = nsList::"Count"
                   if _language = "C#"
                       invoke builder::"AppendFormat"(_csUsingFormat, nsList::"Item"(i) as object)
                   end-if
                   if _language = "C++/CLI: pure"
                       set currentLine to string::"Format"(_cppUsingFormat, nsList::"Item"(i) as object)
                       invoke builder::"Append"(currentLine::"Replace"(".", "::"))
                   end-if
                   if _language = "VB"
                       invoke builder::"AppendFormat"(_vbImportsFormat, nsList::"Item"(i) as object)
                   end-if
                   add 1 to i
               end-perform
               
               set namespaces to builder::"ToString"()
               set builder to new StringBuilder

               set currentLine to reader::"ReadLine"()
               perform with test before until currentLine = null
       
                   if currentLine = "..." and nonMain = string::"Empty"
                       set nonMain to builder::"ToString"()
                       set builder to new StringBuilder
                   else
                       if includeSnippetLineNumbers
                           if _language = "C#"
                               invoke builder::"AppendFormat"(_csLinePragmaFormat, lineNumber)
                           end-if
                           if _language = "C++/CLI: pure"
                               invoke builder::"AppendFormat"(_cppLinePragmaFormat, lineNumber)
                           end-if
                           if _language = "VB"
                               invoke builder::"AppendFormat"(_vbLinePragmaFormat, lineNumber)
                           end-if
                       end-if
                       invoke builder::"AppendFormat"(_lineFormat, currentLine)
                       add 1 to lineNumber
                   end-if
                   
                   set currentLine to reader::"ReadLine"()

               end-perform
               
               if _language = "C#"
                   set result to string::"Concat"(namespaces, _csClassProlog, nonMain,
                       _csMainProlog, builder::"ToString"(), _csMainEpilog, _csClassEpilog)
               end-if
               if _language = "C++/CLI: pure"
                   set result to string::"Concat"(namespaces, _cppClassProlog, nonMain,
                       _cppMainProlog, builder::"ToString"(), _cppMainEpilog, _cppClassEpilog)
               end-if
               if _language = "VB"
                   set result to string::"Concat"(namespaces, _vbModuleProlog, nonMain,
                       _VbMainProlog, builder::"ToString"(), _vbMainEpilog, _vbModuleEpilog)
               end-if
           end method "GenerateCode".

           method-id. "Compile" public.
           working-storage section.
           01  compiler    CodeDomProvider.
           01  ex          type "System.Exception".
           01  msg         string.
           01  caption     string.
           01  newline     string value type "System.Environment"::"NewLine".
           01  parameters  CompilerParameters value new CompilerParameters.
           01  assemblies  type "System.Collections.Generic.List"[string].
           01  assembly    string.
           01  suffix      string.
           01  prefix      string.
           01  executable  string value Application::"ExecutablePath".
           01  baseDir     string value Path::"GetDirectoryName"(executable).
           01  sourceCode  string.
           01  i           binary-long.
           procedure division returning result as CompilerResults.       

           try
               set compiler to self::"Compiler"
           catch ex
               continue
           finally
               if compiler = null
                   set msg to string::"Concat"("Error: Failed get a CodeDomProvider for ", _language, ". ", newline,
                       "Please verify that the compiler is installed.")
                   set caption to "Error"
                   invoke MessageBox::"Show"(msg, caption)
                   set result to null
                   goback
               end-if
           end-try
                              
               set parameters::"WarningLevel" to 4
               set parameters::"GenerateExecutable" to false
               set parameters::"GenerateInMemory" to true

               set assemblies to _options::"Assemblies" as type "System.Collections.Generic.List"[string]
               
               set i to 0
               perform with test before until i = assemblies::"Count"

                   set assembly to assemblies::"Item"(i)

                   if assembly::"EndsWith"(".exe")
                       set suffix to string::"Empty"
                   else
                       set suffix to ".dll"
                   end-if

                   if assembly::"StartsWith"("System")
                       set prefix to string::"Empty"
                   else
                       set prefix to string::"Concat"(baseDir, "\")
                   end-if
                   
                   invoke parameters::"ReferencedAssemblies"::"Add"(string::"Concat"(prefix, assembly, suffix))
                   add 1 to i

               end-perform
               
               set sourceCode to self::"GenerateCode"(true)
               set result to compiler::"CompileAssemblyFromSource"(parameters, sourceCode)

           end method "Compile".
           
           method-id. "Export" public.
           procedure division using location as string.
               invoke type "System.IO.File"::"WriteAllText"(location, self::"GenerateCode"(false))
           end method "Export".

           method-id. "Save" public.
           procedure division using location as string.
               invoke type "System.IO.File"::"WriteAllText"(location, _snippetText)
           end method "Save".

       end object.
       end class Snippet.